package MapRelated;

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import Database.Database;
import TomcatRelated.TomcatProtocolImp;
import TomcatRelated.UserProfile;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.Toast;
import androidApplication.javaFiles.R;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;

public class MyMapView extends MapView {

	//***************************************Class Variables*******************************************

	private final String tag = new String("MyMapView");

	private LocationManager _locManager;
	private LocationListener _locListener;
	private UsersOverlay _usersOverlay;
	private MyDeviceOverlay _myDeviceOverlay;
	private Location _currentBestLocation;
	private GeoPoint _oldMapCenter = null;
	private int _oldZoomLevel = -1;
	
	private Handler handler = new Handler();
	private static final int TWO_MINUTES = 1000 * 60 * 2;
	public static final int ONE_MINUTE = 1000 * 60; // in ms

	private Runnable mapUpdate = new Runnable()
	{
		public void run()
		{
			Log.v(tag, "Updating map with new icons");
			updateUsersOverlay();

			handler.removeCallbacks(mapUpdate); // remove the old callback
			handler.postDelayed(mapUpdate, ONE_MINUTE); // register a new one
		}
	};


	//***************************************Constructors*******************************************
	public MyMapView(android.content.Context context, android.util.AttributeSet attrs) {
		super(context, attrs);
		initComponents();
	}

	public MyMapView(android.content.Context context, android.util.AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initComponents();
	}

	public MyMapView(android.content.Context context, java.lang.String apiKey) {
		super(context, apiKey);
		initComponents();
	}

	//***************************************Public Methods*******************************************
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (ev.getAction() == MotionEvent.ACTION_UP) {

			GeoPoint newMapCenter = getMapCenter();
			
			if(newMapCenter != null)
			{
				if(!newMapCenter.equals(_oldMapCenter))
				{
					Log.v(tag, "update icons now (map moved)");
					_oldMapCenter = newMapCenter;
					updateUsersOverlay();
				}
			}

		}
		
		return super.onTouchEvent(ev);
	}

	@Override
	public void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		
		int newZoomLevel = getZoomLevel();
		
		if (newZoomLevel != _oldZoomLevel) {
			//do your thing
			Log.v(tag, "update icons now(zoom changed)");
			_oldZoomLevel = newZoomLevel;
			updateUsersOverlay();
		}
	}

	//***************************************Private Methods*******************************************
	private void initComponents()
	{
		//Initiating all the objects that used later for the GPS support.
		initLocationObjects();

		//adding the built-in zoom buttons.
		addZoomToMap();

		//adding an overlay that pops the coordinates onClick on the map.
		//		addBasicOverlay();

		//initiating the overlay so that afterwards we could add the current position icon.
		initMyDeviceOverlay();
	}


	public void initAfterConnection()
	{
		//focusing the map on the last known location of the phone.
		focusMapOnCurrentLocation();

		//activating the GPS listeners.
		startLocationUpdates();

		//Adding the icons overlay to the map.
		initUsersOverlay();

		//starting a timer which updates the IconsOverlay every one minute.
		handler.post(mapUpdate); //to receive map update every minute...
	}


	//init the objects that used to get the GPS location
	private void initLocationObjects() 
	{
		Log.v(tag, "Starting Location Objects");
		// Acquire a reference to the system Location Manager
		this._locManager = (LocationManager) this.getContext().getSystemService(Context.LOCATION_SERVICE);

		// Define a listener that responds to location updates
		this._locListener = new LocationListener() {

			/**
			 * Called when a new location is found by the network location provider.
			 */
			public void onLocationChanged(Location location) 
			{
				if (isBetterLocation(location,_currentBestLocation)){
					_currentBestLocation = location;
					Log.v(tag, "Location changed from "+location.getProvider());
					Toast.makeText(getContext(), "location changed!("+location.getProvider()+")",Toast.LENGTH_LONG).show();
					double latitude = location.getLatitude();
					double longitude = location.getLongitude();

					GeoPoint newPosition = getGeoPointFromCoordinate(latitude, longitude);
					updateMyDeviceOverlay(newPosition);

					String my_android_id = Database.getInstance(getContext()).get_device_id();
					//sending update to server
					TomcatProtocolImp.getInstance().updatePosition(my_android_id, latitude, longitude);
				}
			}

			/** 
			 * This method is called when a provider is unable to fetch a location or if the
			 * provider has recently become available after a period of unavailability. 
			 */
			public void onStatusChanged(String provider, int status, Bundle extras) 
			{
				Log.v(tag, "status changed");
			}

			public void onProviderEnabled(String provider) 
			{
				Runnable run = new Runnable() {
					public void run() {
						createOkDialog("GPS Signal is on").show();
					}
				};
				Thread trd = new Thread(run);
				trd.run();

			}

			public void onProviderDisabled(String provider) 
			{

				Log.v(tag, "Provider Disabled");
				Runnable run = new Runnable() {
					public void run() {
						createOkDialog("No GPS Signal, check your settings.").show();
					}
				};
				Thread trd = new Thread(run);
				trd.run();


			}
		};
	}


	//focus the map on the coordinates recieverd. 
	//if zoom is between 1 to 21, other values will cause no change in zoom
	private void focusMapOnPoint(String[] coordinates, int zoom) 
	{
		MapController mapController = this.getController();

		GeoPoint gp = getGeoPointFromCoordinate(coordinates);

		mapController.animateTo(gp);
		if (zoom > 0 && zoom < 22){
			mapController.setZoom(zoom);
		}
		this.postInvalidate();
	}


	public static GeoPoint getGeoPointFromCoordinate(String[] coordinates)
	{
		double lat = Double.parseDouble(coordinates[0]);
		double lng = Double.parseDouble(coordinates[1]);

		return new GeoPoint((int)(lat * 1E6), (int)(lng * 1E6));
	}


	public static GeoPoint getGeoPointFromCoordinate(double latitude, double longitude)
	{
		return new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6));
	}


	private Dialog createOkDialog(String msg) {
		AlertDialog.Builder builder;
		builder = new AlertDialog.Builder(this.getContext());
		builder.setMessage(msg)
		.setCancelable(false) //(so the user cannot close the dialog with the back button) 
		.setNeutralButton("OK", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				dialog.cancel();
			}
		});
		AlertDialog alert = builder.create();
		return alert;
	}


	//Setting the zoom button & setting the map initially invisible.
	private void addZoomToMap() 
	{
		this.setBuiltInZoomControls(true);
	}


	//focus the map on current location based on network location
	private void focusMapOnCurrentLocation() 
	{
		//String coordinates[] = {"31.644029", "34.956665"}; //coordinates of Israel
		_currentBestLocation = this._locManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
		if (_currentBestLocation == null)
		{
			_currentBestLocation = this._locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
			if(_currentBestLocation == null)
			{
				return;	//we don't have any last known location! we don't focus map and don't update the server.		
			}

		}

		// last known location was found! we focus on the map and update the app server.
		double latitude = _currentBestLocation.getLatitude();
		double longitude = _currentBestLocation.getLongitude();
		String coordinates[] = {String.valueOf(latitude),String.valueOf(longitude)};
		TomcatProtocolImp.getInstance().updatePosition(Database.getInstance(getContext()).get_device_id(),
				latitude, longitude);
		focusMapOnPoint(coordinates,8);
	}


	private void addBasicOverlay()
	{
		Overlay overlay = new BasicMapOverlay();
		this.getOverlays().add(overlay);

		this.postInvalidate();
	}


	private void initMyDeviceOverlay()
	{
		Context context = this.getContext();
		Resources resources = context.getResources();
		Drawable _icon = resources.getDrawable(R.drawable.blue_pin);
		_icon.setBounds(0, 0, _icon.getIntrinsicWidth(), _icon.getIntrinsicHeight());
		_myDeviceOverlay = new MyDeviceOverlay(_icon);

		String coordinates[] = {"32.644029", "34.956665"}; //coordinates of sea?
		GeoPoint invisible = MyMapView.getGeoPointFromCoordinate(coordinates);
		_myDeviceOverlay.addItem(new InvisiblePointOverlayItem(invisible, "Invisible Point", null, this));

		List<Overlay> listOfOverlays = this.getOverlays();
		listOfOverlays.add(_myDeviceOverlay);
	}


	private void updateMyDeviceOverlay(GeoPoint position)
	{
		Log.v(tag, "entered updateMyDeviceOverlay");

		if(_myDeviceOverlay == null)
		{
			return; 
		}

		List<Overlay> listOfOverlays = this.getOverlays();

		_myDeviceOverlay.clear();

		String coordinates[] = {"32.644029", "34.956665"}; //coordinates of sea?
		GeoPoint invisible = MyMapView.getGeoPointFromCoordinate(coordinates);
		_myDeviceOverlay.addItem(new InvisiblePointOverlayItem(invisible, "Invisible Point", null, this));

		if (position != null)
		{
			_myDeviceOverlay.addItem(new OverlayItem(position, "My Position", null));

			if(!listOfOverlays.contains(_myDeviceOverlay))
			{
				listOfOverlays.add(_myDeviceOverlay);
			}
		}
		this.postInvalidate();

		Log.v(tag, "finished updateMyDeviceOverlay");
	}


	private void initUsersOverlay()
	{
		Context context = this.getContext();
		Resources resources = context.getResources();
		Drawable _icon = resources.getDrawable(R.drawable.overlay_item);
		_icon.setBounds(0, 0, _icon.getIntrinsicWidth(), _icon.getIntrinsicHeight());
		_usersOverlay = new UsersOverlay(_icon, this);

		String coordinates[] = {"32.644029", "34.956665"}; //coordinates of sea?
		GeoPoint invisible = MyMapView.getGeoPointFromCoordinate(coordinates);
		_usersOverlay.addItem(new InvisiblePointOverlayItem(invisible, "Invisible Point", null, this));

		List<Overlay> listOfOverlays = this.getOverlays();
		listOfOverlays.add(_usersOverlay);
	}


	private void updateUsersOverlay()
	{
		if(_usersOverlay == null) //the registration didn't finish yet, and initIconsOverlay() wasn't called yet.
		{
			return;
		}

		_usersOverlay.removeAllItems(); //initializing the icons ArrayList

		String coordinates[] = {"32.644029", "34.956665"}; //coordinates of sea?
		GeoPoint invisible = MyMapView.getGeoPointFromCoordinate(coordinates);
		_usersOverlay.addItem(new InvisiblePointOverlayItem(invisible, "Invisible Point", null, this));

		//could be constant....
		Display display = ((WindowManager) this.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
		int width = display.getWidth();
		int height = display.getHeight();


		GeoPoint bottom_right = this.getProjection().fromPixels(width, height);
		GeoPoint upper_left = this.getProjection().fromPixels(0, 0);
		Log.v(tag, "addIcons = ("+upper_left.getLatitudeE6()/1E6+","+upper_left.getLongitudeE6()/1E6+") , ("+
				bottom_right.getLatitudeE6()/1E6+","+bottom_right.getLongitudeE6()/1E6+")");

		//sends a request to server to get users in screen
		Vector<UserProfile> allUsers = TomcatProtocolImp.getInstance().getUsersPosition(
				upper_left.getLatitudeE6()/1E6,upper_left.getLongitudeE6()/1E6,
				bottom_right.getLatitudeE6()/1E6,bottom_right.getLongitudeE6()/1E6);

		if (allUsers != null && allUsers.size() != 0){
			Log.v(tag, "******** adding icons to map");

			String myDeviceId = Database.getInstance(this.getContext()).get_device_id();

			for (Iterator<UserProfile> iterator = allUsers.iterator(); iterator.hasNext();) 
			{
				UserProfile user = (UserProfile) iterator.next();
				GeoPoint point = getGeoPointFromCoordinate(user.getX(), user.getY());
				if (!user.get_android_id().equals(myDeviceId))
				{
					_usersOverlay.addItem(_usersOverlay.new MyOverlayItem(point, "Israel", user.get_android_id()));
					//TODO - change the title of the item from Israel to address (in the future)
				}
			}
		}
		else
		{
			Log.v(tag, "********* no icons to add to map");
		}
		this.postInvalidate();
	}

	private void startLocationUpdates()
	{
		// Register the listener with the Location Manager to receive location updates
		Log.v(tag, "Starting Location Updates");
		this._locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 10, _locListener);
		//time is in milisec. 1000milisec = 1sec. here the time is 60,000 milisec = 1 min
		//the distance is in meters. here we will get updates if the device moved 10 meter
		//the supplied LocationListener will be called with the current Location or with status updates.

		//getting location from Wi-Fi
		this._locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 60000, 10, _locListener);
	}


	private void stopLocationUpdates()
	{
		// Remove the listener you previously added
		Log.v(tag, "Stopping Location Updates");
		this._locManager.removeUpdates(this._locListener);
	}



	/** Determines whether one Location reading is better than the current Location fix
	 * @param location  The new Location that you want to evaluate
	 * @param currentBestLocation  The current Location fix, to which you want to compare the new one
	 */
	protected boolean isBetterLocation(Location location, Location currentBestLocation) {
		if (currentBestLocation == null) {
			// A new location is always better than no location
			return true;
		}

		// Check whether the new location fix is newer or older
		long timeDelta = location.getTime() - currentBestLocation.getTime();
		boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
		boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
		boolean isNewer = timeDelta > 0;

		// If it's been more than two minutes since the current location, use the new location
		// because the user has likely moved
		if (isSignificantlyNewer) {
			return true;
			// If the new location is more than two minutes older, it must be worse
		} else if (isSignificantlyOlder) {
			return false;
		}

		// Check whether the new location fix is more or less accurate
		int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
		boolean isLessAccurate = accuracyDelta > 0;
		boolean isMoreAccurate = accuracyDelta < 0;
		boolean isSignificantlyLessAccurate = accuracyDelta > 200;

		// Check if the old and new location are from the same provider
		boolean isFromSameProvider = isSameProvider(location.getProvider(),
				currentBestLocation.getProvider());

		// Determine location quality using a combination of timeliness and accuracy
		if (isMoreAccurate) {
			return true;
		} else if (isNewer && !isLessAccurate) {
			return true;
		} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
			return true;
		}
		return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
		if (provider1 == null) {
			return provider2 == null;
		}
		return provider1.equals(provider2);
	}

}
